﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FireStarter.Base;
using FireStarter.Base.BusinessObjectMerger;
using Firestarter.DB.Schema.BO;
using Firestarter.DB.Schema.Logic;
using Firestarter.DB_DomainModel.Logic;
using FireStarterModeller.BO;
using FireStarterModeller.Logic;
using FireStarterModeller.Logic.FsStartLoading;
using Habanero.Base;
using Habanero.BO;
using Habanero.BO.ClassDefinition;
using Habanero.BO.Loaders;
using NUnit.Framework;

namespace Firestarter.DB.Schema.Test.Logic
{
	// ReSharper disable InconsistentNaming
	[TestFixture]
	public class TestDBReverseEngToFstart_AcceptanceTest
	{

	    private TestUtilsDB_SqlServer _utilsDb;

	    [TestFixtureSetUp]
		public virtual void TestFixtureSetup()
		{
			ClassDef.ClassDefs.Clear();
			string xmlClassDefs = BOBroker.GetClassDefsXml();
			ClassDef.ClassDefs.Add(new XmlClassDefsLoader(xmlClassDefs, new DtdLoader()).LoadClassDefs());
			BORegistry.DataAccessor = new DataAccessorInMemory();

			SchemaMerger.SetupMergeableBORegistry();
		}

        [TearDown]
        public virtual void TearDown()
        {
            if (_utilsDb != null) _utilsDb.DropDatabase();
        }

		[Test]
		public void Test_EmptySolution_DBRevEngFromScript_TestFS_OneTableOneUC_OneUCProp_ExpectedMatchFstartIs_TestFS_OneTableOneUC_OneUCProp()
		{
			//----Setup
            FMDataStore.Reset();

		    // Drop database if it already exists
            _utilsDb = new TestUtilsDB_SqlServer { TestDatabaseName = "TestFS_OneTableOneUC_OneUCProp" };
		    _utilsDb.DropDatabase();

		    //CreateDB
			var scriptRunner = new SqlScriptRunner(_utilsDb);
		    scriptRunner.ExecuteScriptAgainstMasterDB(GetDBRevEngDBScriptsDirectory() + @"TestFS_OneTablesOneUC_OneUCProp.sql");

            //CreateBlankSolution
            var actualSolution = new SolutionCreator().CreateSolution();
		    var startingFstartDataStore = FMDataStore.FMDataStoreInMemory;
		    
            //Setup Reverse Engineering
            var dbtoDMReverseEngineeringController = new DBToDMReverseEngineeringController(actualSolution);
            var reverseEngineering = dbtoDMReverseEngineeringController.CreateReverseEngineeringForSolution(
                                        new ReverseEngineeringSettings(),
                                        DatabaseConnectionInfo.CreateFromDatabaseConfig(_utilsDb.CreateTestDatabaseConfig()));

		    //-------------Assert Preconditions -------------

			//---------------Execute Test ----------------------
            var databaseReverseEnginneringController = new DatabaseReverseEnginneringController(reverseEngineering);
		    databaseReverseEnginneringController.RunMerge();
		    databaseReverseEnginneringController.ImportDBChanges();

            //---------------Test Result -----------------------
            //Load standard fsstart from file
            var expectedFstartDataStore = LoadDataStoreFromFstart(GetDBRevEngExpectedFstartsDirectory() + @"TestFS_OneTableOneUC_OneUCProp.fstart");

            //Get Standard solution
            DMSolution expectedSolution = expectedFstartDataStore.AllObjects.Values.OfType<DMSolution>().First();

            MergeDataStores(startingFstartDataStore, expectedFstartDataStore);

		    Assert.AreEqual(1, actualSolution.AllClasses.Count);
		    Assert.AreEqual("ParentTable", actualSolution.AllClasses[0].ClassNameBO);
		    Assert.AreEqual(reverseEngineering.StoredDatabase.Tables[0].Columns.Count, actualSolution.AllClasses[0].Properties.Count);

		    //---Execute
            //Compare Solution
            // This is needed as the SolutionDirectory of the solution created using the SolutionCreator will not match that of the saved fstart file 
            // loaded into the expected solution
            actualSolution.SolutionDirectory = expectedSolution.SolutionDirectory;

            var mergeBO = MergeableBORegistry.Instance.Resolve<DMSolution>();
            CompareResult result = mergeBO.CompareBusinessObjects(expectedSolution, actualSolution);

            //----Assert Result
            Assert.IsTrue(result.AreEqual, result.CompareFailures());
		    //TODO Brett 02 Nov 2010: This will not work reliably if the above assert fails then the DB will not be dropped.
            // Either in a try catch or drop immediatlye after doing RunMerge.
            _utilsDb.DropDatabase();
		}

        [TestCase("TestFS_OneTableOneUC_OneUCProp", "TestFS_OneTablesOneUC_OneUCProp.sql", "TestFS_OneTableOneUC_OneUCProp.fstart", Description = "C0001 - Import table with UC")]
        [TestCase("TestFS_OneTablesOneUC_TwoUCProps", "TestFS_OneTablesOneUC_TwoUCProps.sql", "TestFS_OneTablesOneUC_TwoUCProps.fstart", Description = "C0001 - Import table with composite UC")]
        [TestCase("TestFS_TwoTablesOneRelationship", "TestFS_TwoTablesOneRelationship.sql", "TestFS_TwoTablesOneRelationship.fstart", Description = "C00005 - Import Relationship")]
        [TestCase("TestFS_TwoTablesTwoRelationshipBetweenThem", "TestFS_TwoTablesTwoRelationshipBetweenThem.sql", "TestFS_TwoTablesTwoRelationshipBetweenThem.fstart", Description = "C00005A - Import Relationship")]
        public void Test_DBRevEngFromScript_SolutionFromEmptySolution_ShouldMatch_SolutionFromFinalFstart(string databaseName, string dbScript, string finalFstart)
        {
            //----Setup
            FMDataStore.Reset();

            // Drop database if it already exists
            _utilsDb = new TestUtilsDB_SqlServer { TestDatabaseName = databaseName };
            _utilsDb.DropDatabase();

            //CreateDB
            var scriptRunner = new SqlScriptRunner(_utilsDb);
            scriptRunner.ExecuteScriptAgainstMasterDB(GetDBRevEngDBScriptsDirectory() + dbScript);

            //CreateBlankSolution
            var actualSolution = new SolutionCreator().CreateSolution();
            var startingFstartDataStore = FMDataStore.FMDataStoreInMemory;

            //Setup Reverse Engineering
            var dbtoDMReverseEngineeringController = new DBToDMReverseEngineeringController(actualSolution);
            var reverseEngineering = dbtoDMReverseEngineeringController.CreateReverseEngineeringForSolution(
                                        new ReverseEngineeringSettings(),
                                        DatabaseConnectionInfo.CreateFromDatabaseConfig(_utilsDb.CreateTestDatabaseConfig()));

            //-------------Assert Preconditions -------------

            //---------------Execute Test ----------------------
            var databaseReverseEnginneringController = new DatabaseReverseEnginneringController(reverseEngineering);
            databaseReverseEnginneringController.RunMerge();
            databaseReverseEnginneringController.ImportDBChanges();

            //---------------Test Result -----------------------
            //Load standard fsstart from file
            var expectedFstartDataStore = LoadDataStoreFromFstart(GetDBRevEngExpectedFstartsDirectory() + finalFstart);

            //Get Standard solution
            DMSolution expectedSolution = expectedFstartDataStore.AllObjects.Values.OfType<DMSolution>().First();

            MergeDataStores(startingFstartDataStore, expectedFstartDataStore);

            //---Execute
            //Compare Solution
            // This is needed as the SolutionDirectory of the solution created using the SolutionCreator will not match that of the saved fstart file 
            // loaded into the expected solution
            actualSolution.SolutionDirectory = expectedSolution.SolutionDirectory;

            var mergeBO = MergeableBORegistry.Instance.Resolve<DMSolution>();
            CompareResult result = mergeBO.CompareBusinessObjects(expectedSolution, actualSolution);

            //----Assert Result
            Assert.IsTrue(result.AreEqual, result.CompareFailures());
        }




	    [Test]
        public void Test_C00003_StandardSolution_DBRevEngFromScript_TestFS_OneTableOneUC_OneUCProp_ExpectedMatchFstartIs_TestFS_OneTableOneUC_OneUCProp()
        {
            //----Setup
            FMDataStore.Reset();

            // Drop database if it already exists
            _utilsDb = new TestUtilsDB_SqlServer { TestDatabaseName = "TestFS_OneTablesOneUC_TwoUCProps" };
            _utilsDb.DropDatabase();

            //CreateDB
            var scriptRunner = new SqlScriptRunner(_utilsDb);
            scriptRunner.ExecuteScriptAgainstMasterDB(GetDBRevEngDBScriptsDirectory() + @"TestFS_OneTablesOneUC_TwoUCProps.sql");

            //Load standard initial fsstart from file
            var startingFstartDataStore = LoadDataStoreFromFstart(GetDBRevEngExpectedFstartsDirectory() + @"TestFS_OneTableOneUC_OneUCProp.fstart");
            // Load initial actual solution
            DMSolution integratedSolution = startingFstartDataStore.AllObjects.Values.OfType<DMSolution>().First();

            
            //This is necessary since the solution name is not changed since the solution name should not be modified during the importing of a Database.

            BORegistry.DataAccessor = new NonValidatingDataAccessorInMemory(startingFstartDataStore);
            Assert.AreEqual(1, integratedSolution.AllClasses.Count);

             
            
            var dbtoDMReverseEngineeringController = new DBToDMReverseEngineeringController(integratedSolution);
            var reverseEngineering = dbtoDMReverseEngineeringController.CreateReverseEngineeringForSolution(
                                        new ReverseEngineeringSettings(),
                                        DatabaseConnectionInfo.CreateFromDatabaseConfig(_utilsDb.CreateTestDatabaseConfig()));

            Assert.AreEqual(reverseEngineering.SolutionID, integratedSolution.SolutionID);
            NonValidatingDataAccessorInMemory dataAccessor = BORegistry.DataAccessor as NonValidatingDataAccessorInMemory;
            var dmSolution = dataAccessor.DataStoreInMemory.Find<DMSolution>(Criteria.FromPrimaryKey(integratedSolution.ID));
            Assert.AreSame(integratedSolution, dmSolution);
            Assert.IsNotNull(integratedSolution.ReverseEngineering.StoredDatabase);
            Assert.AreSame(reverseEngineering.StoredDatabase, integratedSolution.ReverseEngineering.StoredDatabase);

            var initialDB = reverseEngineering.StoredDatabase;
            Assert.IsNotNull(initialDB);
            var dbTable = dataAccessor.DataStoreInMemory.Find<DBTable>(Criteria.FromPrimaryKey( initialDB.Tables[0].ID));
            Assert.AreSame(dbTable, initialDB.Tables[0]);

            var dmClass = dataAccessor.DataStoreInMemory.Find<DMClass>(Criteria.FromPrimaryKey(integratedSolution.AllClasses[0].ID));
            Assert.AreSame(dmClass, integratedSolution.AllClasses[0]);



            var databaseConnectionInfo = DatabaseConnectionInfo.CreateFromDatabaseConfig(_utilsDb.CreateTestDatabaseConfig());
            reverseEngineering.DatabaseConnectionInfo = databaseConnectionInfo;

            //-------------Assert Preconditions -------------

            //---------------Execute Test ----------------------
            var schemaMerger = new SchemaMerger(reverseEngineering);
            schemaMerger.RunMerge();

            IDBDatabase database = reverseEngineering.StoredDatabase;
            DMSolution solution = reverseEngineering.GetSolution();

            Assert.AreEqual(1, database.Tables.Count);
            Assert.AreEqual("ParentTable", database.Tables[0].TableName);
            Assert.AreEqual(1, integratedSolution.AllClasses.Count);
            Assert.AreEqual(database.Tables[0].TableID, integratedSolution.AllClasses[0].MappedTableID);
            IDBChangesImporter dbChangesImporter = GetDBChangesImporter(solution);
            dbChangesImporter.ImportDBChanges(database, solution);
            Assert.AreSame(solution, integratedSolution);
            Assert.AreEqual(1, solution.AllClasses.Count);


            Assert.AreEqual(1, integratedSolution.AllClasses.Count);
            //---------------Test Result -----------------------

            //Load standard expected fsstart from file
            var expectedFstartDataStore = LoadDataStoreFromFstart(GetDBRevEngExpectedFstartsDirectory() + @"TestFS_OneTablesOneUC_TwoUCProps.fstart");
            //Get expected solution
            DMSolution expectedSolution = expectedFstartDataStore.AllObjects.Values.OfType<DMSolution>().First();
            expectedSolution.SolutionName = integratedSolution.SolutionName;
            //Merge expected objects into the DataStore
            MergeDataStores(startingFstartDataStore, expectedFstartDataStore);
            Assert.AreEqual(1, expectedSolution.AllClasses.Count);

            Assert.IsTrue(integratedSolution.AllClasses.Count>0);
            Assert.IsNotNullOrEmpty(integratedSolution.AllClasses[0].ClassNameBO);
            //Assert.AreEqual(reverseEngineering.StoredDatabase.Tables.Count, actualSolution.AllClasses.Count);

            //---Execute
            //Compare Solution
            // This is needed as the SolutionDirectory of the solution created using the SolutionCreator will not match that of the saved fstart file 
            // loaded into the expected solution
            integratedSolution.SolutionDirectory = expectedSolution.SolutionDirectory;
            var mergeBO = MergeableBORegistry.Instance.Resolve<DMSolution>();
            CompareResult result = mergeBO.CompareBusinessObjects(expectedSolution, integratedSolution);

            //----Assert Result
            Assert.IsTrue(result.AreEqual, result.CompareFailures());

            //TODO andrew 03 Nov 2010: I would really like an additional test that tests the total number of objects expected in the datastore at the end
            // the reason is that I do not want eroneous objects in the datastore by mistake.
            // The issue with this is the end user (e.g. Kelly) would not be able to know what this number is?

        }

        [TestCase("TestFS_OneTablesOneUC_TwoUCProps", "TestFS_OneTablesOneUC_TwoUCProps.sql", "TestFS_OneTableOneUC_OneUCProp.fstart", "TestFS_OneTablesOneUC_TwoUCProps.fstart", Description = "C00003 - Add UC Prop")]
        [TestCase("TestFS_OneTableOneUC_OneUCProp", "TestFS_OneTablesOneUC_OneUCProp.sql", "TestFS_OneTablesOneUC_TwoUCProps.fstart", "TestFS_OneTableOneUC_OneUCProp.fstart", Description = "C00004 - Remove UC Prop")]
        [TestCase("TestFS_TwoTablesOneRelationship", "TestFS_TwoTablesOneRelationship.sql", "TestFS_TwoTablesTwoRelationshipBetweenThem.fstart", "TestFS_TwoTablesOneRelationship.fstart", Description = "C00006 - Remove Relationship")]
        public void Test_DBRevEngFromScript_SolutionFromInitialFstart_ShouldMatch_SolutionFromFinalFstart(string databaseName, string dbScript, string initialFstart, string finalFstart)
        {
            //----Setup
            FMDataStore.Reset();

            // Drop database if it already exists
            _utilsDb = new TestUtilsDB_SqlServer { TestDatabaseName = databaseName };
            _utilsDb.DropDatabase();

            //CreateDB
            var scriptRunner = new SqlScriptRunner(_utilsDb);
            scriptRunner.ExecuteScriptAgainstMasterDB(GetDBRevEngDBScriptsDirectory() + dbScript);

            //Load standard initial fsstart from file
            var startingFstartDataStore = LoadDataStoreFromFstart(GetDBRevEngExpectedFstartsDirectory() + initialFstart);
            BORegistry.DataAccessor = new NonValidatingDataAccessorInMemory(startingFstartDataStore);

            // Load initial actual solution
            var integratedSolution = startingFstartDataStore.AllObjects.Values.OfType<DMSolution>().First();
            BORegistry.DataAccessor = new NonValidatingDataAccessorInMemory(startingFstartDataStore);


            var dbtoDMReverseEngineeringController = new DBToDMReverseEngineeringController(integratedSolution);
            // We need to update the database connection info for the reverse engineering as we want to point to the named database not the database associated with the inital fstart
            var reverseEngineering = dbtoDMReverseEngineeringController.CreateReverseEngineeringForSolution(
                                        new ReverseEngineeringSettings(),
                                        DatabaseConnectionInfo.CreateFromDatabaseConfig(_utilsDb.CreateTestDatabaseConfig()));


            Assert.AreSame(reverseEngineering, integratedSolution.ReverseEngineering);
            Assert.AreSame(reverseEngineering.DatabaseConnectionInfo, integratedSolution.ReverseEngineering.DatabaseConnectionInfo);
            // We need to update the database connection info for the reverse engineering as we want to point to the named database not the database associated with the inital fstart
            var databaseConnectionInfo = DatabaseConnectionInfo.CreateFromDatabaseConfig(_utilsDb.CreateTestDatabaseConfig());

            Assert.AreNotSame(databaseConnectionInfo, reverseEngineering.DatabaseConnectionInfo);

            reverseEngineering.DatabaseConnectionInfo = databaseConnectionInfo;
            DMSolution solution = reverseEngineering.GetSolution();
            //-------------Assert Preconditions -------------

            //---------------Execute Test ----------------------
            var databaseReverseEnginneringController = new DatabaseReverseEnginneringController(reverseEngineering);
            databaseReverseEnginneringController.RunMerge();
            databaseReverseEnginneringController.ImportDBChanges();

            //---------------Test Result -----------------------
            Assert.AreSame(solution, integratedSolution);

            //Load standard expected fsstart from file
            var expectedFstartDataStore = LoadDataStoreFromFstart(GetDBRevEngExpectedFstartsDirectory() + finalFstart);
            //Get expected solution
            DMSolution expectedSolution = expectedFstartDataStore.AllObjects.Values.OfType<DMSolution>().First();
            expectedSolution.SolutionName = integratedSolution.SolutionName;
            //Merge expected objects into the DataStore
            MergeDataStores(startingFstartDataStore, expectedFstartDataStore);

            Assert.IsTrue(integratedSolution.AllClasses.Count > 0);
            Assert.IsNotNullOrEmpty(integratedSolution.AllClasses[0].ClassNameBO);

            //---Execute
            //Compare Solution
            // This is needed as the SolutionDirectory of the solution created using the SolutionCreator will not match that of the saved fstart file 
            // loaded into the expected solution
            integratedSolution.SolutionDirectory = expectedSolution.SolutionDirectory;
            var mergeBO = MergeableBORegistry.Instance.Resolve<DMSolution>();
            CompareResult result = mergeBO.CompareBusinessObjects(expectedSolution, integratedSolution);

            //----Assert Result
            Assert.IsTrue(result.AreEqual, result.CompareFailures());
        }


	    private static void MergeDataStores(DataStoreInMemory mergeToDataStore, DataStoreInMemory mergeFromDataStore)
	    {
            foreach (var bo in mergeFromDataStore.AllObjects.Values)
	        {
	            mergeToDataStore.Add(bo);
	        }
	    }

	    private static IDBChangesImporter GetDBChangesImporter(DMSolution solution)
	    {
	        DBToDMImporterFactory dbToDMImporterFactory = new DBToDMImporterFactory(new DBTypeToDMPropertyTypeMapper(new PropertyTypeStorer(solution)), new RelationshipNameGenerator());
	        return dbToDMImporterFactory.CreateDBChangesImporter();
	    }

        #region Get Directory

        private static string AssemblyDirectory
        {
            get
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                string path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
        }

        private static string GetFireStarterModellerDirectory()
        {
            return AssemblyDirectory.Substring(0, AssemblyDirectory.IndexOf("FireStarter_Modeller")) + @"FireStarter_Modeller\";
        }

        private static string GetDBRevEngExpectedFstartsDirectory()
        {
            return GetFireStarterModellerDirectory() + @"trunk\TestScripts\DatabaseReverseEngineering\ExpectedfsStarts\";
        }

        private static string GetDBRevEngDBScriptsDirectory()
        {
            return GetFireStarterModellerDirectory() + @"trunk\TestScripts\DatabaseReverseEngineering\DBScripts\";
        }


        #endregion



	    [Ignore("This test is just here to figure out how to get the inmemory stuff to play nicely together")] //TODO Andrew Russell 03 Nov 2010: Ignored Test - This test is just here to figure out how to get the inmemory stuff to play nicely together
	    [Test]
	    public void Test_Method()
	    {
	        //---------------Set up test pack-------------------
	        
	        //---------------Assert Precondition----------------
            FMDataStore.Reset();
            FMDataStore.FMDataStoreInMemory.AllObjects = new FsStartLoader().LoadDataStoreFromFile(@"F:\Systems\Habanero Tools\FireStarter_Modeller\TestScripts\DatabaseReverseEngineering\ExpectedfsStarts\TestFS_OneTableOneUC_OneUCProp.fstart");
	        //---------------Execute Test ----------------------

            var expectedSolution = FMDataStore.FMDataStoreInMemory.Find<DMSolution>(new Criteria("SolutionName", Criteria.ComparisonOp.Equals,
                                                                        "TestFS_OneTableOneUC_OneUCProp"));
	        //---------------Test Result -----------------------
	        Assert.AreEqual(1, expectedSolution.AllClasses.Count);
	    }


        private static DataStoreInMemory LoadDataStoreFromFstart(string fileName)
        {
            var loadDataStoreFromFile = new FsStartLoader().LoadDataStoreFromFile(fileName);
            return new DataStoreInMemory { AllObjects = loadDataStoreFromFile };
        }

	}



    public class DatabaseReverseEnginneringController   
    {
        private readonly IReverseEngineering _reverseEngineering;

        public DatabaseReverseEnginneringController(IReverseEngineering reverseEngineering)
        {
            _reverseEngineering = reverseEngineering;
        }

        public void RunMerge()
        {
            var schemaMerger = new SchemaMerger(_reverseEngineering);
            schemaMerger.RunMerge();
        }

        public void ImportDBChanges()
        {

            IDBChangesImporter dbChangesImporter = GetDBChangesImporter(_reverseEngineering.GetSolution());
            dbChangesImporter.ImportDBChanges(_reverseEngineering.StoredDatabase, _reverseEngineering.GetSolution());
        }

        private static IDBChangesImporter GetDBChangesImporter(IDMSolution solution)
        {
            DBToDMImporterFactory dbToDMImporterFactory = new DBToDMImporterFactory(new DBTypeToDMPropertyTypeMapper(new PropertyTypeStorer(solution)), new RelationshipNameGenerator());
            return dbToDMImporterFactory.CreateDBChangesImporter();
        }
    }


    // ReSharper restore InconsistentNaming


/*	public class ReverseEngAcceptanceHarness
	{
		public ReverseEngAcceptanceHarness(FsStartLoader fstartLoader, SqlScriptReader sqlScriptReader)
		{
		}

		public void LoadActualSolution(string fstartFileName = "")
		{
			throw new NotImplementedException();
		}

		public void LoadExpectedSolution(string fstartFileName)
		{
			throw new NotImplementedException();
		}

		public void ReverseEngineer()
		{
			throw new NotImplementedException();
		}

		public bool CompareSolutions()
		{
			throw new NotImplementedException();
		}

		public void CreateSqlDatabase(string sqlScript)
		{
			throw new NotImplementedException();
		}
	}*/
}
